package org.zn.note.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    // 可以不覆盖这个方法，默认就是一个User用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /* 内存用户存储
        // 这里加两个用户
        auth
                // 基于内存的用户存储，一般测试用；如果要增删改，需要修改代码
                // （还可以基于JDBC用户存储、LDAP为后端的用户存储、自定义用户详情服务）
                .inMemoryAuthentication()
                .withUser("buzz").password("buzz").authorities("ADMIN")
                .and()
                .withUser("woody").password("woody").authorities("ADMIN");
        */

        /* JDBC 用户存储
        // 这样写就是用默认的相关表，需要数据库中有这些表
//        auth.jdbcAuthentication().dataSource(dataSource); // 基于JDBC的存储

        // 也可以自己定义查询SQL，这样就能和自己的用户系统结合了
        // 查询和结果集都有要求的，只能按username查询，然后只能返回这样的结果集
        String sqlUser = "select username, password, enabled from Users where username=?";
        String sqlAuth = "select username, authority from UserAuthorities where username=?";
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery(sqlUser)
                .authoritiesByUsernameQuery(sqlAuth)
//        .passwordEncoder() // 可以定义一个密码加解密转换器
//        .groupAuthoritiesByUsername(sql) // 可以设置群组权限sql
        ;
        */

        /* LDAP作为后端的用户存储，Lightweight Directory Access Protocol， 轻量级目录访问协议
        需要LDAP服务器，开发的话也可以用内置的
        auth.ldapAuthentication()
                .userSearchBase("ou=people") // 基础目录，不从根目录开始
                .userSearchFilter("(uid={0})") // 过滤
                .groupSearchBase("ou=groups")
                .groupSearchFilter("member={0}")
                .passwordCompare()
//                .passwordEncoder()
                .passwordAttribute("passcode") // 默认找password，这个定义找指定属性
        ;
        */

        // 更常用的是自定义用户存储
        auth
                .userDetailsService(userDetailsService)
                // 这里的encoder()会被拦截！会传入Bean
                .passwordEncoder(encoder());
    }

    // 配置http各url权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 先都不校验
        http.authorizeRequests().antMatchers("/", "/**").permitAll();

        /* ---------- 先关闭权限这块功能 ----------

        // 对于url权限，是有先后顺序的，在前面的优先级高(生效)
        // 如果 permitAll 在最前面，后面配置什么都没用了
        // 除了hasRole、permitAll，还有很多配置
        http.authorizeRequests()
                // 访问“/design, /orders”，需要ADMIN权限
                .antMatchers("/design", "/orders").hasRole("ADMIN")
                // 其他路径，不需要权限，谁都可以访问
                .antMatchers("/", "/**").permitAll()
                .and() // 完成授权配置，再添加一些其他配置
                // 不用默认权限验证页面，用自己的(登录页)
                // 默认预期要有username、password，也可配置
                .formLogin() // 开始登入设置
                .loginPage("/login") // 自定义权限验证页面(登录页)
                .loginProcessingUrl("/authenticate") // 用这个来处理login提交的请求
                .usernameParameter("user") // 默认(页面提交的)属性名username，这里定义为user
                .passwordParameter("pwd") // 同上
                // 默认的，验证成功后会调回之前的页面；如果直接访问验证页面，则成功后跳转到根路径(主页面)
                // 这里可以设置成功后的跳转路径
                .defaultSuccessUrl("/design", true) // true 不管之前访问什么页面，成功后都强制跳转到这个页面
                .and()
                .logout() // 开始登出设置（会拦截 /logout 请求）
                .logoutSuccessUrl("/") // 退出后，跳转到主页
//                .and()
//                .csrf() // 默认是带csrf安全的，可以配置
        ;

        // 下面这样写和上面等价，但是用access(SpEL)可以更灵活
//        http
//                .authorizeRequests()
//                .antMatchers("/design", "/orders").access("hasRole('ADMIN')")
//                .antMatchers("/", "/**").access("permitAll");

        ---------- 先关闭权限这块功能 ---------- */
    }

    @Bean
    public PasswordEncoder encoder() {
        return new StandardPasswordEncoder("53cr3t");
    }
}
